package algo_annotations.heuristique;

import algo_annotations.data.annotations.GroupeAnno;
import algo_annotations.data.sites.SousGroupeSite;
import commons.logs.Logger;
import commons.utils.Utils;
import commons.utils.UtilsCalcul;
import java.util.Arrays;


public class Vivier {

	private boolean bUseMultiligne;
	private int tailleVivier;
	public int getTailleVivier() {return tailleVivier;}

	private Genome[] tabGenome;

	private SousGroupeSite ssGrpSite;

	//copie du meilleur genome d'anno
	private Genome bestGenome;
	public Genome getBestGenome() {return bestGenome;}
	public double getNoteBestGenome() {return bestGenome.getNoteGlobale();}

	public Vivier(SousGroupeSite ssGrpSite, int tailleVivier, boolean bUseMultiligne) {
		this.bUseMultiligne = bUseMultiligne;
		this.tailleVivier = tailleVivier;
		this.ssGrpSite = ssGrpSite;

		// Création des génomes du vivier
		tabGenome = new Genome[tailleVivier];
	    for (int i=0 ; i<tailleVivier ; i++)
	        tabGenome[i] = new Genome(ssGrpSite.size(), bUseMultiligne);

		bestGenome = new Genome(ssGrpSite.size(), bUseMultiligne);
	}


	public void setAllRandom() {
	    for (int i=0 ; i<tailleVivier ; i++)
			tabGenome[i].setFullRandomGenes();
	}


	// Il y a 1 grpAnno par CPU : celui fourni correspond a celui "appartenant" au thread courant
	// Il comprend toutes les annos (donc pas uniquement celles du sous-groupe)
	public void computeNotes(GroupeAnno grpAnno, int indexDeb, int indexFin) {
		assert (indexDeb>=0 && indexFin<tailleVivier && indexDeb<=indexFin);

	    for (int i=indexDeb ; i<=indexFin ; i++) {
			Genome gen = tabGenome[i];
			grpAnno.computeNote_forGenome(ssGrpSite, gen);
		}
	}


	public void sortGenomes() {
		Arrays.sort(tabGenome);
		bestGenome = tabGenome[0];
	}

	public void modifyVivier(int numIter, Evolution evolMUTATION, Evolution evolCROSSOVER, Evolution evolTAKEBEST, Evolution evolADJUST) {

		Logger.log_DEBUG1(" - Modification vivier");

		// Cas spécial de la 1ere itération
		if (numIter==0) {
			Logger.log_DEBUG1("   -> fst iter : Full Random");
			//On part d'une situation entièrement aléatoire
			setAllRandom();
			return;
		}

		//**********************************************************************
		//********* Ajustement des paramètres **********************************
		double poidsMUTATION = evolMUTATION.getPoidsForThisIter(numIter);
		double poidsCROSSOVER = evolCROSSOVER.getPoidsForThisIter(numIter);
		double poidsTAKEBEST = evolTAKEBEST.getPoidsForThisIter(numIter);
		double poidsADJUST = evolADJUST.getPoidsForThisIter(numIter);

		double sommePoids = poidsMUTATION + poidsCROSSOVER + poidsTAKEBEST + poidsADJUST;
		if (sommePoids==0) {
			Logger.log_WARN("   -> rien à modifier dans le vivier !!!!!!");
			return;
		}

		double prctMUTATION = poidsMUTATION / sommePoids;
		double prctCROSSOVER = poidsCROSSOVER / sommePoids;
		double prctTAKEBEST = poidsTAKEBEST / sommePoids;
		double prctADJUST = poidsADJUST / sommePoids;

		int NB_MUTATION;
		int NB_CROSSOVER;
		int NB_TAKE_BEST;
		int NB_ADJUST;

		int NB_KEEP_BEST = 1;
		NB_KEEP_BEST = Math.min(NB_KEEP_BEST, tailleVivier);

		// c'est sur ce reste qu'on va calculer les %
		int nbRestant = tailleVivier - NB_KEEP_BEST;
		NB_MUTATION = (int) UtilsCalcul.round(nbRestant * prctMUTATION, 0);
		NB_CROSSOVER = (int) UtilsCalcul.round(nbRestant * prctCROSSOVER, 0);
		NB_TAKE_BEST = (int) UtilsCalcul.round(nbRestant * prctTAKEBEST, 0);
		NB_ADJUST  = (int) UtilsCalcul.round(nbRestant * prctADJUST, 0);

		// correction des éventuelles erreurs d'arrondi
		int nbTrop = (NB_KEEP_BEST + NB_MUTATION + NB_CROSSOVER + NB_TAKE_BEST + NB_ADJUST) - tailleVivier;
		if (nbTrop>0) {
			// on tente de rogner sur le MUTATION, ou sur les autres si pas possible
			// avec une taille vivier > 20 il ne devrait pas y avoir de probleme
			if (NB_MUTATION>=nbTrop) NB_MUTATION-=nbTrop;
			else if (NB_CROSSOVER>=nbTrop) NB_CROSSOVER-=nbTrop;
			else if (NB_TAKE_BEST>=nbTrop) NB_TAKE_BEST-=nbTrop;
			else if (NB_ADJUST>=nbTrop) NB_ADJUST-=nbTrop;
			else if (NB_KEEP_BEST>=nbTrop) NB_KEEP_BEST-=nbTrop;
			else assert false : "Impossible de corriger l'erreur d'arrondi pour la modification du vivier";
		} else if (nbTrop<0) {
			NB_ADJUST += (-nbTrop);
		}

		Logger.log_DEBUG1("   -> Mutation  = " + NB_MUTATION + "(" + UtilsCalcul.round(evolMUTATION.getParamForThisIter(numIter),3) + ")");
		Logger.log_DEBUG1("   -> CrossOver = " + NB_CROSSOVER + "(" + UtilsCalcul.round(evolCROSSOVER.getParamForThisIter(numIter),3) + ")");
		Logger.log_DEBUG1("   -> Take Best = " + NB_TAKE_BEST + "(" + UtilsCalcul.round(evolTAKEBEST.getParamForThisIter(numIter),3) + ")");
		Logger.log_DEBUG1("   -> Adjust    = " + NB_ADJUST + "(" + UtilsCalcul.round(evolADJUST.getParamForThisIter(numIter),3) + ")");
		Logger.log_DEBUG1("   -> Keep Best = " + NB_KEEP_BEST);

		// dernière verif au cas ou
		assert(NB_MUTATION + NB_CROSSOVER + NB_TAKE_BEST + NB_ADJUST + NB_KEEP_BEST == tailleVivier);


		//**********************************************************************
		//*******  Mise à jour du génome ***************************************
		Genome[] tabGenomeCopy = createTabGenomeCopy();

		int count = 0;
		double param;

		//---  méthodes violentes et aléatoires ---
		param = evolMUTATION.getParamForThisIter(numIter);
		for (int i=0; i<NB_MUTATION; i++)
			tabGenome[count++].randomMutate(param);


		param = evolCROSSOVER.getParamForThisIter(numIter);
		for (int i=0; i<NB_CROSSOVER; i++)
			if (i+1 < tailleVivier)
				tabGenome[count++].becomeCrossOverOf(tabGenomeCopy[i], tabGenomeCopy[i+1], param);
			else
				tabGenome[count++].becomeCrossOverOf(tabGenomeCopy[i], tabGenomeCopy[i/2], param);


		//--- méthodes subtiles pour se rapprocher finement du résultat ---
		param = evolTAKEBEST.getParamForThisIter(numIter);
		for (int i=0; i<NB_TAKE_BEST; i++)
			if (i+1 < tailleVivier)
				tabGenome[count++].takeBestFrom(tabGenomeCopy[i], tabGenomeCopy[i+1], param);
			else
				tabGenome[count++].takeBestFrom(tabGenomeCopy[i], tabGenomeCopy[i/2], param);


		param = evolADJUST.getParamForThisIter(numIter);
		for (int i=0; i<NB_ADJUST; i++)
			tabGenome[count++].becomeAdjustedOf(tabGenomeCopy[i], param);



		// C7 (introduction d'une roue biaisée : bof bof
//		createTabGenomeCopy();
//		createRoueBiaisee();
//
//		int count = 0;
//		double param;
//
//		//---  méthodes violentes et aléatoires ---
//		param = evolMUTATION.getParamForThisIter(numIter);
//		for (int i=0; i<NB_MUTATION; i++)
//			tabGenome[count++].becomeRandomMutate(takeOneGoodGenome(), param);
//
//
//		param = evolCROSSOVER.getParamForThisIter(numIter);
//		for (int i=0; i<NB_CROSSOVER; i++) {
//			Genome papa = takeOneGoodGenome();
//			Genome mama = takeOneGoodGenome();
//			tabGenome[count++].becomeCrossOverOf(papa, mama, param);
//		}
//
//
//		//--- méthodes subtiles pour se rapprocher finement du résultat ---
//		param = evolTAKEBEST.getParamForThisIter(numIter);
//		for (int i=0; i<NB_TAKE_BEST; i++) {
//			Genome papa = takeOneGoodGenome();
//			Genome mama = takeOneGoodGenome();
//			tabGenome[count++].takeBestFrom(papa, mama, param);
//		}
//
//		param = evolADJUST.getParamForThisIter(numIter);
//		for (int i=0; i<NB_ADJUST; i++)
//			tabGenome[count++].becomeAdjustedOf(takeOneGoodGenome(), param);



		for (int i=0; i<NB_KEEP_BEST; i++)
			tabGenome[count++].becomeCopyOf(tabGenomeCopy_temp[i]);

		
		// Reinitialisation des notes (utilité ? puisqu'elles vont être toutes recalculées ...)
		for (Genome g : tabGenome)
			g.resetNote();
	}


	// Renvoie une copy de tabGenom
	private Genome[] tabGenomeCopy_temp = null;
	private Genome[] createTabGenomeCopy() {
		// Creation initiale, fait qu'une fois
		if (tabGenomeCopy_temp==null) {
			tabGenomeCopy_temp = new Genome[tailleVivier];
			int tailleGenome = tabGenome[0].getTaille();
			for (int i=0; i<tailleVivier ; i++)
				tabGenomeCopy_temp[i] = new Genome(tailleGenome, bUseMultiligne);
		}

		for (int i=0; i<tailleVivier ; i++)
			tabGenomeCopy_temp[i].becomeCopyOf(tabGenome[i]);

		return tabGenomeCopy_temp;
	}

	// Il faut avoir appelé createTabGenomeCopy() avant !!
	private double[] tabRoueBiaisee = null;
	private double[] createRoueBiaisee() {
		if (tabRoueBiaisee == null)
			tabRoueBiaisee = new double[tailleVivier];

		//tabRoueBiaisee[0] = (1-tabGenomeCopy_temp[0].getNoteGlobale());
		tabRoueBiaisee[0] = tailleVivier*tailleVivier;
		for (int i=1; i<tailleVivier ; i++) {
			//tabRoueBiaisee[i] = tabRoueBiaisee[i-1] + (1-tabGenomeCopy_temp[i].getNoteGlobale());
			tabRoueBiaisee[i] = tabRoueBiaisee[i-1] + (tailleVivier-i)*(tailleVivier-i);
		}
		return tabRoueBiaisee;
	}

	// Il faut avoir appelé createRoueBiaisee() avant !!
	private Genome takeOneGoodGenome() {
		double valMax = tabRoueBiaisee[tailleVivier-1];
		double rnd = Utils.rnd()*valMax;
		for (int i=0; i<tailleVivier ; i++) {
			if (rnd < tabRoueBiaisee[i])
				return tabGenomeCopy_temp[i];
		}
		return tabGenomeCopy_temp[0];
	}


	// aide le GC
	void dispose() {
		tabGenomeCopy_temp = null;
		tabGenome = null;
	}


}
